/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kie.dmn.feel.parser.feel11;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.kie.dmn.api.feel.runtime.events.FEELEvent;
import org.kie.dmn.feel.lang.CompositeType;
import org.kie.dmn.feel.lang.impl.FEELEventListenersManager;
import org.kie.dmn.feel.lang.impl.JavaBackedType;
import org.kie.dmn.feel.lang.Scope;
import org.kie.dmn.feel.lang.SimpleType;
import org.kie.dmn.feel.lang.Symbol;
import org.kie.dmn.feel.lang.Type;
import org.kie.dmn.feel.lang.types.BuiltInType;
import org.kie.dmn.feel.lang.types.ScopeImpl;
import org.kie.dmn.feel.lang.types.SymbolTable;
import org.kie.dmn.feel.lang.types.VariableSymbol;
import org.kie.dmn.feel.runtime.FEELFunction;
import org.kie.dmn.feel.runtime.Range;
import org.kie.dmn.feel.runtime.UnaryTest;
import org.kie.dmn.feel.runtime.events.UnknownVariableErrorEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.stream.Collectors;
public class ParserHelper {
public static final Logger LOG = LoggerFactory.getLogger(ParserHelper.class);
private FEELEventListenersManager eventsManager;
private SymbolTable symbols = new SymbolTable();
private Scope currentScope = symbols.getGlobalScope();
private Stack<String> currentName = new Stack<>();
private int dynamicResolution = 0;
public ParserHelper() {
this( null );
}
public ParserHelper(FEELEventListenersManager eventsManager) {
this.eventsManager = eventsManager;
// initial context is loaded
currentName.push( Scope.LOCAL );
}
public SymbolTable getSymbolTable() {
return symbols;
}
public void pushScope() {
LOG.trace("pushScope()");
currentScope = new ScopeImpl( currentName.peek(), currentScope );
}
public void pushScope(Type type) {
LOG.trace("pushScope()");
currentScope = new ScopeImpl( currentName.peek(), currentScope, type );
}
public void popScope() {
LOG.trace("popScope()");
currentScope = currentScope.getParentScope();
}
public void pushName( String name ) {
LOG.trace("pushName() {}", name);
this.currentName.push( name );
}
public void pushName(ParserRuleContext ctx) {
this.currentName.push( getOriginalText( ctx ) );
}
public void popName() {
LOG.trace("popName()");
this.currentName.pop();
}
public void recoverScope() {
recoverScope( currentName.peek() );
}
public void recoverScope( String name ) {
LOG.trace("[{}] recoverScope( name: {}) with currentScope: {}", this.currentScope.getName(), name, currentScope);
Scope s = this.currentScope.getChildScopes().get( name );
if( s != null ) {
currentScope = s;
if ( currentScope.getType() != null && currentScope.getType().equals(BuiltInType.UNKNOWN) ) {
enableDynamicResolution();
}
} else {
Symbol resolved = this.currentScope.resolve(name);
if ( resolved != null && resolved.getType() instanceof CompositeType ) {
pushName(name);
pushScope(resolved.getType());
CompositeType type = (CompositeType) resolved.getType();
for ( Map.Entry<String, Type> f : type.getFields().entrySet() ) {
this.currentScope.define(new VariableSymbol( f.getKey(), f.getValue() ));
}
LOG.trace(".. PUSHED, scope name {} with symbols {}", this.currentName.peek(), this.currentScope.getSymbols());
} else if ( resolved != null && resolved.getType() instanceof BuiltInType ) {
BuiltInType resolvedBIType = (BuiltInType) resolved.getType();
pushName(name);
pushScope(resolvedBIType);
switch (resolvedBIType) {
// FEEL spec table 53
case DATE:
this.currentScope.define(new VariableSymbol( "year", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "month", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "day", BuiltInType.NUMBER ));
break;
case TIME:
this.currentScope.define(new VariableSymbol( "hour", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "minute", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "second", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "time offset", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "timezone", BuiltInType.NUMBER ));
break;
case DATE_TIME:
this.currentScope.define(new VariableSymbol( "year", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "month", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "day", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "hour", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "minute", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "second", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "time offset", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "timezone", BuiltInType.NUMBER ));
break;
case DURATION:
// TODO might need to distinguish between `years and months duration` and `days and time duration`
this.currentScope.define(new VariableSymbol( "years", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "months", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "days", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "hours", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "minutes", BuiltInType.NUMBER ));
this.currentScope.define(new VariableSymbol( "seconds", BuiltInType.NUMBER ));
break;
// table 53 applies only to type(e) is a date/time/duration
case UNKNOWN:
enableDynamicResolution();
break;
default:
break;
}
} else {
pushScope();
}
}
}
public void dismissScope() {
LOG.trace("dismissScope()");
if ( currentScope.getType() != null && currentScope.getType().equals(BuiltInType.UNKNOWN) ) {
disableDynamicResolution();
}
popScope();
}
public void validateVariable( ParserRuleContext ctx, List<String> qn, String name ) {
if( eventsManager != null && !isDynamicResolution() ) {
if( this.currentScope.getChildScopes().get( name ) == null && this.currentScope.resolve( name ) == null ) {
// report error
FEELEventListenersManager.notifyListeners( eventsManager , () -> {
String varName = qn.stream().collect( Collectors.joining( "." ) );
return new UnknownVariableErrorEvent( FEELEvent.Severity.ERROR,
"Unknown variable '" + varName + "'",
ctx.getStart().getLine(),
ctx.getStart().getCharPositionInLine(),
varName );
}
);
}
}
}
public boolean isDynamicResolution() {
return dynamicResolution > 0;
}
public void disableDynamicResolution() {
if( dynamicResolution > 0 ) {
this.dynamicResolution--;
}
}
public void enableDynamicResolution() {
this.dynamicResolution++;
}
public void defineVariable(ParserRuleContext ctx) {
defineVariable( getOriginalText( ctx ) );
}
public void defineVariable(String variable) {
VariableSymbol var = new VariableSymbol( variable );
this.currentScope.define( var );
}
public void defineVariable(String variable, Type type) {
LOG.trace("defining custom type symbol.");
VariableSymbol var = new VariableSymbol( variable, type );
this.currentScope.define( var );
}
public void startVariable(Token t) {
this.currentScope.start( t.getText() );
}
public boolean followUp(Token t, boolean isPredict) {
boolean follow = ( isDynamicResolution() && FEELParser.isVariableNamePartValid( t.getText() ) ) || this.currentScope.followUp( t.getText(), isPredict );
return follow;
}
public String getOriginalText(ParserRuleContext ctx) {
int a = ctx.start.getStartIndex();
int b = ctx.stop.getStopIndex();
Interval interval = new Interval( a, b );
return ctx.getStart().getInputStream().getText( interval );
}
public static List<Token> getAllTokens(
ParseTree ctx,
List<Token> tokens) {
for ( int i = 0; i < ctx.getChildCount(); i++ ) {
ParseTree child = ctx.getChild( i );
if ( child instanceof TerminalNode ) {
tokens.add( ((TerminalNode) child).getSymbol() );
} else {
getAllTokens( child, tokens );
}
}
return tokens;
}
public static Type determineTypeFromClass( Class<?> clazz ) {
if( clazz == null ) {
return BuiltInType.UNKNOWN;
} else if( Number.class.isAssignableFrom(clazz) ) {
return BuiltInType.NUMBER;
} else if( String.class.isAssignableFrom(clazz) ) {
return BuiltInType.STRING;
} else if( LocalDate.class.isAssignableFrom(clazz) ) {
return BuiltInType.DATE;
} else if( LocalTime.class.isAssignableFrom(clazz) || OffsetTime.class.isAssignableFrom(clazz) ) {
return BuiltInType.TIME;
} else if( ZonedDateTime.class.isAssignableFrom(clazz) || OffsetDateTime.class.isAssignableFrom(clazz) || LocalDateTime.class.isAssignableFrom(clazz) ) {
return BuiltInType.DATE_TIME;
} else if( Duration.class.isAssignableFrom(clazz) || Period.class.isAssignableFrom(clazz) ) {
return BuiltInType.DURATION;
} else if( Boolean.class.isAssignableFrom(clazz) ) {
return BuiltInType.BOOLEAN;
} else if( UnaryTest.class.isAssignableFrom(clazz) ) {
return BuiltInType.UNARY_TEST;
} else if( Range.class.isAssignableFrom(clazz) ) {
return BuiltInType.RANGE;
} else if( FEELFunction.class.isAssignableFrom(clazz) ) {
return BuiltInType.FUNCTION;
} else if( List.class.isAssignableFrom(clazz) ) {
return BuiltInType.LIST;
} else if( Map.class.isAssignableFrom(clazz) ) { // TODO not so sure about this one..
return BuiltInType.CONTEXT;
}
return JavaBackedType.of( clazz );
}
}